#!/usr/bin/env python3
import asyncio
import signal
import time

from loguru import logger

from core.browser_manager import BrowserManager
from core.command_manager import EventType, CommandRes
from core.logger_setup import setup_logging
from core.state_manager import StateManager
from core.tcp_client import TCPClient
from modules.search import SearchManager
from modules.order import OrderManager
from modules.chat_manager_ws import ChatManagerWS
from utils.configs import APP_PORT, APP_HOST, FILE_RECEIVER_PORT
from core.file_receiver_server import FileReceiverServer
setup_logging()

# 全局变量：用于优雅关闭
shutdown_event = asyncio.Event()
_shutdown_in_progress = False  # 防止重复处理信号

# 初始化全局实例
STATE = StateManager()
browser = BrowserManager(state=STATE)
SEARCH = SearchManager(state=STATE, browser_manager=browser)
ORDER = OrderManager(state=STATE, browser_manager=browser, search_manager=SEARCH)
CHAT = ChatManagerWS(browser_manager=browser, state_manager=STATE)

# ✅ 初始化文件接收服务器（IP白名单将在TCP连接后更新）
FILE_RECEIVER = FileReceiverServer(host='0.0.0.0', port=FILE_RECEIVER_PORT)


async def check_login_background():
    """后台异步检测登录状态，不阻塞TCP连接"""
    try:
        # 等待页面完全加载（给闲鱼自动登录弹窗时间出现）
        logger.info("🔍 后台检测登录状态...")
        await asyncio.sleep(5)
        
        # 第一次检测登录状态
        is_logged_in = await browser.check_login()
        if is_logged_in:
            logger.success(f"✅ 账号已登录: {STATE.get('account')}")
            
            # ✅ 登录成功后，立即发送状态更新（确保前端能识别已登录的客户端）
            if browser.tcp_client:
                try:
                    await browser.tcp_client.send_status(include_cookie=False)
                    logger.debug("已发送登录状态更新")
                except Exception as e:
                    logger.debug(f"发送登录状态更新失败: {e}")
            
            # ✅ 登录成功后，启动IM WebSocket连接
            try:
                logger.info("🔌 正在启动闲鱼IM WebSocket连接...")
                await CHAT.start_im_connection()
                logger.success("✅ 闲鱼IM连接已启动，可以接收和发送消息")
            except Exception as e:
                logger.warning(f"⚠️ 启动IM连接失败: {e}，聊天功能将不可用")
        else:
            logger.info("⚠️  初次检测未登录，继续监控（可能闲鱼正在自动登录）...")
            
            # 如果有cookie但未登录，可能是快速登录弹窗正在出现
            cookies = await browser.get_cookie_dict()
            if cookies and '_m_h5_tk' in cookies:
                logger.info("检测到登录cookie，等待闲鱼快速登录完成...")
                await asyncio.sleep(10)
                
                # 再次检测
                is_logged_in = await browser.check_login()
                if is_logged_in:
                    logger.success(f"✅ 快速登录成功，账号: {STATE.get('account')}")
                    
                    # ✅ 发送状态更新（确保前端能识别已登录的客户端）
                    if browser.tcp_client:
                        try:
                            await browser.tcp_client.send_status(include_cookie=False)
                            logger.debug("已发送快速登录状态更新")
                        except Exception as e:
                            logger.debug(f"发送快速登录状态更新失败: {e}")
                        
                        # ✅ 发送 cookie 更新
                        await browser.tcp_client.send_cookie_update()
                    
                    # ✅ 快速登录成功后，启动IM WebSocket连接
                    try:
                        logger.info("🔌 正在启动闲鱼IM WebSocket连接...")
                        await CHAT.start_im_connection()
                        logger.success("✅ 闲鱼IM连接已启动，可以接收和发送消息")
                    except Exception as e:
                        logger.warning(f"⚠️ 启动IM连接失败: {e}，聊天功能将不可用")
                else:
                    logger.warning("⚠️  Cookie已失效，等待用户重新登录...")
            else:
                logger.warning("⚠️  无登录cookie，等待用户登录...")
    except Exception as e:
        logger.error(f"后台登录检测失败: {e}")


async def check_ban_status_background():
    """
    后台定时检测封禁状态任务
    一开始就和TCP、启动浏览器并行运行
    每8小时检测一次（如果账号被封禁）
    """
    # 等待浏览器启动完成（初始等待30秒）
    await asyncio.sleep(30)
    
    while True:
        try:
            # 检查浏览器是否运行
            if not browser.browser or not browser.page:
                logger.debug("浏览器未运行，跳过封禁检测")
                await asyncio.sleep(3600)  # 等待1小时后重试
                continue
            
            # 检查账号是否被封禁
            if STATE.is_banned():
                # 检查是否应该检测（距离上次检测已满8小时）
                if STATE.should_check_ban_status():
                    logger.info("=" * 70)
                    logger.info("⏰ 后台封禁检测任务：距离上次检测已满8小时，开始检测...")
                    logger.info("=" * 70)
                    
                    # 通过API检测封禁状态
                    is_still_banned = await browser.check_ban_status_from_api()
                    
                    # 更新检测时间
                    STATE.update_ban_check_time()
                    
                    if is_still_banned:
                        logger.warning("❌ 账号仍处于封禁状态，将继续等待解封")
                        banned_at = STATE.get("banned_at")
                        if banned_at:
                            banned_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(banned_at))
                            logger.info(f"封禁开始时间: {banned_time}")
                        
                        # 发送状态更新到控制端
                        if browser.tcp_client:
                            try:
                                await browser.tcp_client.send_status(include_cookie=False)
                                logger.info("✅ 已发送封禁状态更新到控制端")
                            except Exception as e:
                                logger.warning(f"发送封禁状态失败: {e}")
                    else:
                        logger.success("=" * 70)
                        logger.success("✅ 账号已解封！清除封禁状态")
                        logger.success("=" * 70)
                        STATE.clear_banned()
                        
                        # 发送状态更新到控制端
                        if browser.tcp_client:
                            try:
                                await browser.tcp_client.send_status(include_cookie=False)
                                logger.info("✅ 已发送解封状态更新到控制端")
                            except Exception as e:
                                logger.warning(f"发送解封状态失败: {e}")
                    
                    logger.info("=" * 70)
                else:
                    # 未到检测时间，计算剩余时间
                    remaining = STATE.get_ban_remaining_time()
                    hours = remaining / 3600
                    if hours > 0:
                        logger.debug(f"账号处于封禁状态，距离下次检测还有 {hours:.1f} 小时")
                    
                    # 等待一段时间后再次检查（避免频繁检查）
                    await asyncio.sleep(1800)  # 等待30分钟后再次检查
                    continue
            else:
                # 如果账号未被封禁，等待更长时间后再检查
                await asyncio.sleep(3600)  # 等待1小时后再次检查
            
        except asyncio.CancelledError:
            logger.info("后台封禁检测任务被取消")
            break
        except Exception as e:
            logger.error(f"后台封禁检测任务异常: {e}", exc_info=True)
            await asyncio.sleep(3600)  # 出错后等待1小时再重试


async def handle_chat_open(event):
    data = event.data or {}
    seller_id = data.get('seller_id')
    item_id = data.get('item_id')
    seller_name = data.get('seller_name')

    # ✅ 精简日志：chat_open命令太频繁，不记录日志
    if not seller_id:
        logger.warning("[Chat] chat_open失败: 缺少seller_id")
        return CommandRes.ChatOpen().Fail(msg="缺少 seller_id")

    try:
        result = await CHAT.open_chat(seller_id=seller_id, item_id=item_id, seller_name=seller_name)
        # ✅ 确保返回的数据包含seller_id和seller_name（即使为空也返回）
        if not result.get('seller_id'):
            result['seller_id'] = seller_id
        if not result.get('seller_name') and seller_name:
            result['seller_name'] = seller_name
        
        # ✅ 精简日志：chat_open成功不记录日志
        return CommandRes.ChatOpen().Success(data=result)
    except Exception as exc:
        logger.error(f"[Chat] 打开聊天窗口失败: {exc}")
        return CommandRes.ChatOpen().Fail(msg=str(exc))


async def handle_chat_send(event):
    data = event.data or {}
    seller_id = data.get('seller_id')
    item_id = data.get('item_id')
    message = data.get('message')
    temp_id = data.get('temp_id')  # ✅ 消息状态机：前端生成的临时ID
    mid = data.get('mid')  # ✅ 消息状态机：消息mid

    logger.info(f"📥 [Chat] 收到chat_send命令: seller_id={seller_id}, item_id={item_id}, message={message[:50] if message else 'N/A'}..., temp_id={temp_id}, mid={mid}")

    if not seller_id:
        logger.warning("[Chat] chat_send失败: 缺少seller_id")
        return CommandRes.ChatSend().Fail(msg="缺少 seller_id")
    if not message:
        logger.warning("[Chat] chat_send失败: 消息内容为空")
        return CommandRes.ChatSend().Fail(msg="消息内容不能为空")

    try:
        logger.debug(f"[Chat] 开始发送消息: seller_id={seller_id}, message={message[:50]}..., temp_id={temp_id}")
        result = await CHAT.send_message(seller_id=seller_id, item_id=item_id, message=message, temp_id=temp_id, mid=mid)
        return CommandRes.ChatSend().Success(data=result)
    except Exception as exc:
        logger.exception(f"发送聊天消息失败: {exc}")
        return CommandRes.ChatSend().Fail(msg=str(exc))


async def handle_chat_upload_image(event):
    """处理上传图片命令（从本地文件读取，使用Agent的浏览器上下文上传，保持IP一致性）"""
    data = event.data or {}
    file_id = data.get('file_id')  # 文件ID（文件接收服务器返回的）
    file_name = data.get('file_name', 'pasteImg0')
    file_size = data.get('file_size', 0)
    
    # ✅ 接收会话关联信息
    conversation_id = data.get('conversation_id')
    seller_id = data.get('seller_id')
    item_id = data.get('item_id')
    temp_id = data.get('temp_id')  # ✅ 消息状态机：前端生成的临时ID
    mid = data.get('mid')  # ✅ 消息状态机：消息mid
    
    # ✅ 如果提供了conversation_id但没有seller_id，尝试解析
    if conversation_id and not seller_id:
        try:
            if '#' in conversation_id:
                parts = conversation_id.split('#', 1)
                seller_id = parts[0] if parts[0] else seller_id
                item_id = parts[1] if len(parts) > 1 and parts[1] else item_id
        except Exception as e:
            logger.warning(f"解析conversation_id失败: {e}, conversation_id={conversation_id}")
    
    logger.info(f"📥 [Chat] 收到chat_upload_image命令: file_name={file_name}, file_size={file_size}字节, file_id={file_id}, conversation_id={conversation_id}, seller_id={seller_id}, item_id={item_id}, temp_id={temp_id}, mid={mid}")
    
    # ✅ 优先使用file_id（新方案：从文件接收服务器读取）
    if file_id:
        try:
            # ✅ 从文件接收服务器获取文件路径
            file_path = FILE_RECEIVER.get_file_path(file_id)
            if not file_path:
                logger.error(f"文件不存在: file_id={file_id}")
                return CommandRes.ChatUploadImage().Fail(msg=f'文件不存在: {file_id}')
            
            # ✅ 读取文件内容
            image_bytes = file_path.read_bytes()
            logger.info(f"✅ 文件读取成功: {len(image_bytes)}字节, path={file_path}")
            
            # ✅ 上传完成后删除临时文件
            try:
                file_path.unlink()
                if file_id in FILE_RECEIVER.files:
                    del FILE_RECEIVER.files[file_id]
                logger.debug(f"🗑️ 临时文件已删除: {file_id}")
            except Exception as e:
                logger.warning(f"删除临时文件失败: {e}")
            
        except Exception as read_err:
            logger.error(f"读取文件失败: {read_err}", exc_info=True)
            return CommandRes.ChatUploadImage().Fail(msg=f'读取文件失败: {str(read_err)}')
    
    # ✅ 兼容旧方案：base64编码的图片数据（向后兼容）
    elif data.get('image_data'):
        try:
            import base64
            image_data_base64 = data.get('image_data')
            logger.info(f"📥 使用base64数据（旧方案）: data_size={len(image_data_base64)}")
            image_bytes = base64.b64decode(image_data_base64)
        except Exception as decode_err:
            logger.error(f"base64解码失败: {decode_err}", exc_info=True)
            return CommandRes.ChatUploadImage().Fail(msg=f'图片数据解码失败: {str(decode_err)}')
    else:
        logger.warning("[Chat] chat_upload_image失败: 缺少文件数据（file_id或image_data）")
        return CommandRes.ChatUploadImage().Fail(msg="缺少文件数据")
    
    try:
        # ✅ 使用aiohttp直接上传图片（使用state中的cookie，不依赖浏览器）
        # 这样可以避免浏览器崩溃导致的上传失败，提高可靠性
        import aiohttp
        
        # ✅ 从state获取cookie
        cookies = STATE.get("cookie", {})
        
        # ✅ 构建Cookie请求头（与goofish_api.py中的方式一致）
        cookie_header_parts = []
        if cookies:
            for name, value in cookies.items():
                cookie_value = value if value is not None else ""
                cookie_header_parts.append(f"{name}={cookie_value}")
        
        cookie_header = "; ".join(cookie_header_parts) if cookie_header_parts else ""
        
        # ✅ 构建multipart/form-data（使用aiohttp.FormData）
        form_data = aiohttp.FormData()
        form_data.add_field('file', 
                          image_bytes,
                          filename=file_name,
                          content_type='image/png')
        
        # ✅ 使用aiohttp上传图片（使用state中的cookie，保持IP一致性）
        upload_url = 'https://stream-upload.goofish.com/api/upload.api?floderId=0&appkey=xy_chat&_input_charset=utf-8'
        
        headers = {
            'accept': '*/*',
            'accept-language': 'zh-CN,zh;q=0.9',
            'cache-control': 'no-cache',
            'pragma': 'no-cache',
            'referer': 'https://www.goofish.com/im',
            'sec-fetch-dest': 'empty',
            'sec-fetch-mode': 'cors',
            'sec-fetch-site': 'same-site',
        }
        
        # ✅ 添加Cookie请求头
        if cookie_header:
            headers['Cookie'] = cookie_header
        
        # ✅ 使用aiohttp上传（不依赖浏览器）
        async with aiohttp.ClientSession() as session:
            async with session.post(
                upload_url,
                data=form_data,
                headers=headers,
                timeout=aiohttp.ClientTimeout(total=30)
            ) as response:
                if response.status != 200:
                    error_text = await response.text()
                    logger.error(f"图片上传失败: status={response.status}, error={error_text[:500]}")
                    return CommandRes.ChatUploadImage().Fail(msg=f'上传失败: HTTP {response.status}')
                
                result = await response.json()
        
        if result.get('success') and result.get('object'):
            upload_result = result['object']
            # ✅ 解析图片尺寸
            pix = upload_result.get('pix', '')
            width = None
            height = None
            if pix and 'x' in pix:
                try:
                    parts = pix.split('x')
                    width = int(parts[0]) if parts[0] else None
                    height = int(parts[1]) if len(parts) > 1 and parts[1] else None
                except (ValueError, IndexError):
                    pass
            
            logger.info(f"✅ 图片上传成功: url={upload_result.get('url')}, size={width}x{height}")
            
            # ✅ 如果提供了seller_id和item_id，自动发送图片消息
            if seller_id and upload_result.get('url'):
                try:
                    logger.info(f"📤 自动发送图片消息: seller_id={seller_id}, item_id={item_id}")
                    await CHAT.send_image_message(
                        seller_id=seller_id,
                        item_id=item_id,
                        image_url=upload_result.get('url'),
                        image_width=width,
                        image_height=height,
                        temp_id=temp_id,
                        mid=mid
                    )
                    logger.info(f"✅ 图片消息已自动发送")
                except Exception as send_err:
                    logger.error(f"自动发送图片消息失败: {send_err}", exc_info=True)
                    # 即使发送失败，也返回上传成功的结果，让前端可以手动发送
            
            return CommandRes.ChatUploadImage().Success(data={
                'url': upload_result.get('url'),
                'width': width,
                'height': height,
                'file_id': upload_result.get('fileId'),
                'size': upload_result.get('size'),
                'seller_id': seller_id,  # ✅ 返回seller_id和item_id用于前端关联
                'item_id': item_id
            })
        else:
            error_msg = result.get('msg', '上传失败')
            logger.error(f"图片上传失败: {error_msg}")
            return CommandRes.ChatUploadImage().Fail(msg=error_msg)
            
    except Exception as exc:
        logger.exception(f"上传图片失败: {exc}")
        return CommandRes.ChatUploadImage().Fail(msg=str(exc))


async def handle_chat_send_image(event):
    """处理发送图片消息命令"""
    data = event.data or {}
    seller_id = data.get('seller_id')
    item_id = data.get('item_id')
    image_url = data.get('image_url')
    image_width = data.get('image_width')
    image_height = data.get('image_height')
    temp_id = data.get('temp_id')  # ✅ 消息状态机：前端生成的临时ID
    mid = data.get('mid')  # ✅ 消息状态机：消息mid

    logger.info(f"📥 [Chat] 收到chat_send_image命令: seller_id={seller_id}, item_id={item_id}, image_url={image_url}, temp_id={temp_id}, mid={mid}")

    if not seller_id:
        logger.warning("[Chat] chat_send_image失败: 缺少seller_id")
        return CommandRes.ChatSendImage().Fail(msg="缺少 seller_id")
    if not image_url:
        logger.warning("[Chat] chat_send_image失败: 图片URL为空")
        return CommandRes.ChatSendImage().Fail(msg="图片URL不能为空")

    try:
        logger.debug(f"[Chat] 开始发送图片: seller_id={seller_id}, image_url={image_url}, temp_id={temp_id}")
        await CHAT.send_image_message(
            seller_id=seller_id,
            item_id=item_id,
            image_url=image_url,
            image_width=image_width,
            image_height=image_height,
            temp_id=temp_id,
            mid=mid
        )
        return CommandRes.ChatSendImage().Success(data={
            "seller_id": seller_id,
            "item_id": item_id,
            "image_url": image_url
        })
    except Exception as exc:
        logger.exception(f"发送图片消息失败: {exc}")
        return CommandRes.ChatSendImage().Fail(msg=str(exc))


async def handle_chat_read(event):
    """处理已读回执命令"""
    data = event.data or {}
    seller_id = data.get('seller_id')
    item_id = data.get('item_id')
    message_ids = data.get('message_ids', [])
    
    logger.debug(f"[Chat] 收到chat_read命令: seller_id={seller_id}, message_count={len(message_ids)}")
    
    if not seller_id:
        logger.warning("[Chat] chat_read失败: 缺少seller_id")
        return CommandRes.ChatRead().Fail(msg="缺少 seller_id")
    if not message_ids:
        logger.warning("[Chat] chat_read失败: 消息ID列表为空")
        return CommandRes.ChatRead().Fail(msg="消息ID列表不能为空")
    
    try:
        result = await CHAT.mark_read(seller_id=seller_id, item_id=item_id, message_ids=message_ids)
        return result
    except Exception as exc:
        logger.error(f"[Chat] 发送已读回执失败: {exc}")
        return CommandRes.ChatRead().Fail(msg=str(exc))


async def handle_chat_recall(event):
    """处理撤回消息命令"""
    data = event.data or {}
    seller_id = data.get('seller_id')
    item_id = data.get('item_id')
    message_id = data.get('message_id')
    
    logger.info(f"[Chat] 收到chat_recall命令: seller_id={seller_id}, message_id={message_id}")
    
    if not seller_id:
        logger.warning("[Chat] chat_recall失败: 缺少seller_id")
        return CommandRes.ChatRecall().Fail(msg="缺少 seller_id")
    if not message_id:
        logger.warning("[Chat] chat_recall失败: 消息ID为空")
        return CommandRes.ChatRecall().Fail(msg="消息ID不能为空")
    
    try:
        result = await CHAT.recall_message(seller_id=seller_id, item_id=item_id, message_id=message_id)
        return result
    except Exception as exc:
        logger.error(f"[Chat] 撤回消息失败: {exc}")
        return CommandRes.ChatRecall().Fail(msg=str(exc))


async def handle_load_emoji_list(event):
    """处理加载表情包列表命令（由控制端调用）"""
    logger.info("[Emoji] 收到load_emoji_list命令")
    
    try:
        from modules.goofish_api import GoofishAPIClient
        
        # 使用全局的browser_manager和state_manager
        api_client = GoofishAPIClient(state, browser)
        
        # 调用加载表情包方法
        result = await api_client.load_emoji_list()
        
        if result.get("success"):
            logger.success(f"[Emoji] 表情包加载成功（{len(result.get('items', []))}个表情）")
            return CommandRes.LoadEmoji().Success(data=result)
        else:
            error_msg = result.get("error", "未知错误")
            logger.warning(f"[Emoji] 表情包加载失败: {error_msg}")
            return CommandRes.LoadEmoji().Fail(msg=error_msg, data=result)
            
    except Exception as exc:
        logger.error(f"[Emoji] 加载表情包失败: {exc}")
        return CommandRes.LoadEmoji().Fail(msg=str(exc))


async def main():
    # ✅ 快速启动浏览器，不阻塞TCP连接
    logger.info("=" * 60)
    logger.info("Agent启动中，正在启动浏览器...")
    logger.info("=" * 60)
    
    try:
        # 快速启动浏览器（不等待登录验证）
        await browser.start_browser()
        logger.success("✅ 浏览器启动成功")
    except Exception as e:
        logger.error(f"❌ 启动浏览器失败: {e}")
        logger.warning("将以未登录状态连接到控制端...")
    
    logger.info("=" * 60)
    logger.info("正在连接控制端...")
    
    # ⚠️ 立即连接TCP，不等待登录检测
    client = TCPClient(host=APP_HOST, port=APP_PORT, state=STATE)
    
    # 设置TCP客户端引用，让各模块可以主动发送消息
    browser.set_tcp_client(client)
    CHAT.set_tcp_client(client)
    ORDER.set_tcp_client(client)  # ✅ 设置订单队列的TCP客户端
    
    client.register_handler(EventType.Search, SEARCH.handle_search)
    client.register_handler(EventType.Order, ORDER.handle_order)
    client.register_handler(EventType.OrderCancel, ORDER.handle_cancel_order)
    client.register_handler(EventType.GetAddress, ORDER.handle_get_address)
    client.register_handler(EventType.Start, browser.handle_start)
    client.register_handler(EventType.Stop, browser.handle_stop)
    client.register_handler(EventType.Reset, browser.handle_reset)
    client.register_handler(EventType.Login, browser.handle_login)
    client.register_handler(EventType.ChatOpen, handle_chat_open)
    client.register_handler(EventType.ChatSend, handle_chat_send)
    client.register_handler(EventType.ChatSendImage, handle_chat_send_image)  # ✅ 发送图片消息
    client.register_handler(EventType.ChatUploadImage, handle_chat_upload_image)  # ✅ 上传图片（从文件接收服务器读取并上传到闲鱼）
    client.register_handler(EventType.ChatRead, handle_chat_read)  # ✅ 已读回执
    client.register_handler(EventType.ChatRecall, handle_chat_recall)  # ✅ 撤回消息
    client.register_handler(EventType.LoadEmoji, handle_load_emoji_list)  # ✅ 加载表情包
    
    # 保存后台任务引用，用于清理
    background_tasks = []
    
    try:
        # 🔄 启动后台登录检测任务（不阻塞TCP连接）
        login_task = asyncio.create_task(check_login_background())
        logger.info("已启动后台登录检测任务")
        
        # 🔄 启动后台封禁检测任务（每8小时检测一次）
        ban_task = asyncio.create_task(check_ban_status_background())
        logger.info("已启动后台封禁检测任务（每8小时检测一次）")
        
        # ✅ 启动文件接收服务器（在TCP连接后，从state获取控制端IP并设置白名单）
        async def start_file_receiver_with_whitelist():
            """启动文件接收服务器，并定期更新IP白名单"""
            # 等待TCP连接成功并获取控制端IP
            max_wait = 30  # 最多等待30秒
            waited = 0
            control_ip = None
            while waited < max_wait:
                control_ip = STATE.get('control_server_ip')
                if control_ip:
                    FILE_RECEIVER.update_allowed_ips({control_ip})
                    break
                await asyncio.sleep(1)
                waited += 1
            
            if not control_ip:
                logger.warning("⚠️ 未获取到控制端IP，文件接收服务器将拒绝所有请求（安全默认）")
            
            # 启动文件接收服务器
            await FILE_RECEIVER.start_server()
            
            # 定期检查并更新IP白名单（如果控制端IP变化）
            while not shutdown_event.is_set():
                await asyncio.sleep(10)  # 每10秒检查一次
                current_ip = STATE.get('control_server_ip')
                if current_ip and current_ip not in FILE_RECEIVER.allowed_ips:
                    FILE_RECEIVER.update_allowed_ips({current_ip})
                    logger.info(f"✅ 文件接收服务器IP白名单已更新: {current_ip}")
        
        file_receiver_task = asyncio.create_task(start_file_receiver_with_whitelist())
        logger.info("已启动文件接收服务器任务")
        
        background_tasks = [login_task, ban_task, file_receiver_task]
        
        # ✅ 在主循环中检查shutdown_event
        await client.connect_loop()
    except asyncio.CancelledError:
        logger.info("连接循环被取消")
    except KeyboardInterrupt:
        logger.info("收到键盘中断，开始关闭")
        shutdown_event.set()
    finally:
        # 清理资源：关闭 aiohttp ClientSession（避免资源泄漏警告）
        # ✅ 快速清理资源（并行执行，超时时间很短）
        logger.debug("正在清理资源...")
        
        # ✅ 取消所有后台任务并等待它们完成
        for task in background_tasks:
            if not task.done():
                task.cancel()
        
        # 等待后台任务被取消（带超时）
        if background_tasks:
            try:
                await asyncio.wait_for(
                    asyncio.gather(*background_tasks, return_exceptions=True),
                    timeout=0.3
                )
            except:
                pass
        
        # ✅ 快速关闭资源（并行执行，总超时时间1秒）
        close_tasks = []
        
        if hasattr(SEARCH, 'api_client') and SEARCH.api_client:
            close_tasks.append(SEARCH.api_client.close())
        if hasattr(ORDER, 'api_client') and ORDER.api_client:
            close_tasks.append(ORDER.api_client.close())
        if browser:
            close_tasks.append(browser._cleanup_browser())
        if CHAT:
            close_tasks.append(CHAT.shutdown())
        if FILE_RECEIVER:
            close_tasks.append(FILE_RECEIVER.stop_server())
        
        # 并行执行，总超时时间1秒
        if close_tasks:
            try:
                await asyncio.wait_for(asyncio.gather(*close_tasks, return_exceptions=True), timeout=1.0)
            except:
                pass  # 超时或失败都直接退出，不等待
        
        # ✅ 给事件循环一点时间来清理残留的 Future
        try:
            await asyncio.sleep(0.1)
        except:
            pass
        
        logger.debug("资源清理完成")

def signal_handler(signum, frame):
    """信号处理器：优雅关闭程序"""
    global _shutdown_in_progress
    if _shutdown_in_progress:
        # 如果已经在关闭中，直接退出（不打印日志，避免重复）
        import os
        os._exit(1)
    _shutdown_in_progress = True
    logger.info(f"收到信号 {signum}，开始优雅关闭...")
    shutdown_event.set()

async def main_with_shutdown():
    """带优雅关闭的主函数"""
    # 设置信号处理器（仅Linux/Unix）
    try:
        if hasattr(signal, 'SIGINT'):
            signal.signal(signal.SIGINT, signal_handler)
        if hasattr(signal, 'SIGTERM'):
            signal.signal(signal.SIGTERM, signal_handler)
    except (ValueError, OSError):
        # Windows可能不支持某些信号
        pass
    
    try:
        await main()
    except KeyboardInterrupt:
        logger.info("收到键盘中断信号")
        shutdown_event.set()
    except asyncio.CancelledError:
        logger.info("主任务被取消")
    except Exception as e:
        logger.exception(f"主函数异常: {e}")
        shutdown_event.set()
    finally:
        # ✅ 确保清理完成（但已经在finally块中清理过了）
        logger.debug("程序退出")

if __name__ == "__main__":
    # ✅ 自定义事件循环，避免显示 Playwright 退出时的 Future 异常
    try:
        # 创建新的事件循环
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        
        # ✅ 设置异常处理器，抑制 Playwright 退出时的 Future 异常
        def exception_handler(loop, context):
            exception = context.get('exception')
            message = context.get('message', '')
            
            # ✅ 抑制 Playwright 驱动关闭时的异常
            if exception and 'Connection closed while reading from the driver' in str(exception):
                logger.debug("Playwright 驱动关闭异常已抑制（这是正常的退出流程）")
                return
            
            # 其他异常正常记录
            if exception:
                logger.error(f"事件循环异常: {message}", exc_info=exception)
            else:
                logger.error(f"事件循环异常: {message}")
        
        loop.set_exception_handler(exception_handler)
        
        try:
            # 运行主函数
            loop.run_until_complete(main_with_shutdown())
        finally:
            # ✅ 清理事件循环
            try:
                # 取消所有剩余任务
                pending = asyncio.all_tasks(loop)
                for task in pending:
                    task.cancel()
                
                # 等待任务取消完成（带超时）
                if pending:
                    loop.run_until_complete(asyncio.wait(pending, timeout=1.0))
            except:
                pass
            
            # 关闭事件循环
            loop.close()
    except KeyboardInterrupt:
        logger.info("程序被用户中断")
    except Exception as e:
        logger.exception(f"程序异常退出: {e}")
